﻿using Actor.Net.Location;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace Actor.Net.Binder
{
    /// <summary>
    /// Actor绑定器
    /// </summary>
    public class ActorBinder
    {
        private static Dictionary<string, Type> ActorInterfaceTypeCache { get; } = new Dictionary<string, Type>();
        private static Dictionary<string, Type> ActorTypeCache { get; } = new Dictionary<string, Type>();
        private static Dictionary<Type, ActorTypeInfo> ActorTypeInfoCache { get; } = new Dictionary<Type, ActorTypeInfo>();

        private static Dictionary<Type, Type> ActorMappingInterfaceTypeCache { get; } = new Dictionary<Type, Type>();
        private static Dictionary<Type, Type> ActorInterfaceMappingTypeCache { get; } = new Dictionary<Type, Type>();

        private static Dictionary<string, MethodInfo> ActorRouterMethodCache { get; } = new Dictionary<string, MethodInfo>();
        private static Dictionary<string, string> ActorRouterMappingCache { get; } = new Dictionary<string, string>();
        private static Dictionary<string, ParameterInfo[]> ActorRouterMethodParameterCache { get; } = new Dictionary<string, ParameterInfo[]>();

        private static Dictionary<string, FuncAttribute> ActorFuncMappingCache { get; } = new Dictionary<string, FuncAttribute>();

        private static ConcurrentDictionary<string, IActor> ActorInstantiationCache { get; } = new ConcurrentDictionary<string, IActor>();
        private static ConcurrentDictionary<Type, ConcurrentDictionary<string, IActor>> TypeActorInstantiationCache { get; } = new ConcurrentDictionary<Type, ConcurrentDictionary<string, IActor>>();

        /// <summary>
        /// 绑定Actor实现接口
        /// </summary>
        /// <param name="type">Actor实现类类型</param>
        /// <param name="interfaceType">Actor接口类型</param>
        /// <param name="serverGroup">Actor服务分组</param>
        internal static void Bind(Type type, Type interfaceType, string serverGroup)
        {
            var actorType = type.FullName;
            var actorTypeKey = KeyGenerator.CreateActorTypeKey(serverGroup, actorType);

            ActorTypeCache[actorTypeKey] = type;
            ActorInterfaceTypeCache[actorTypeKey] = interfaceType;
            ActorTypeInfoCache[type] = new ActorTypeInfo { ServerGroup = serverGroup, ActorType = actorType };
            ActorMappingInterfaceTypeCache[type] = interfaceType;
            ActorInterfaceMappingTypeCache[interfaceType] = type;

            BindRouter(type, interfaceType, serverGroup, actorType);
        }

        /// <summary>
        /// 绑定Actor路由
        /// </summary>
        /// <param name="type">Actor实现类类型</param>
        /// <param name="serverGroup">Actor服务分组</param>
        /// <param name="actorType">Actor类型</param>
        private static void BindRouter(Type type, Type interfaceType, string serverGroup, string actorType)
        {
            var interfaceMethods = interfaceType.GetMethods();
            var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance);
            foreach (var method in methods)
            {
                if (!interfaceMethods.Where(m => 
                {
                    if (m.Name != method.Name)
                        return false;

                    if (m.ReturnType != method.ReturnType)
                        return false;

                    var ps1 = m.GetParameters();
                    var ps2 = method.GetParameters();

                    if (ps1 == ps2)
                        return true;

                    if (ps1 == null && ps2 != null)
                        return false;

                    if (ps1 != null && ps2 == null)
                        return false;

                    if (ps1.Length != ps2.Length)
                        return false;

                    for(var i=0; i< ps1.Length; i++)
                    {
                        if (ps1[i].ParameterType != ps2[i].ParameterType)
                            return false;
                    }

                    return true;

                }).Any()) 
                { 
                    continue; 
                }

                var routerAttribute = method.GetCustomAttribute<ActorRouterAttribute>();

                string routerKey = KeyGenerator.CreateActorRouterKey(serverGroup, actorType, method.Name);
                if (routerAttribute == null)
                {
                    ActorRouterMethodCache[routerKey] = method;
                    ActorRouterMappingCache[routerKey] = method.Name;
                }
                else
                {
                    ActorRouterMappingCache[routerKey] = routerAttribute.Router;
                    routerKey = KeyGenerator.CreateActorRouterKey(serverGroup, actorType, routerAttribute.Router);
                    ActorRouterMethodCache[routerKey] = method;
                }

                var returnSetAttribute = method.GetCustomAttribute<FuncAttribute>();
                if(returnSetAttribute != null)
                    ActorFuncMappingCache[routerKey] = returnSetAttribute;

                ActorRouterMethodParameterCache[routerKey] = method.GetParameters();
            }
        }

        /// <summary>
        /// 获取Actor接口值绑定特性
        /// </summary>
        /// <param name="serverGroup">Actor服务分组</param>
        /// <param name="actorType">Actor类型</param>
        /// <param name="router">Actor路由</param>
        /// <returns></returns>
        public static FuncAttribute GetActorExcuteAttribute(string serverGroup, string actorType, string router)
        {
            var routerKey = KeyGenerator.CreateActorRouterKey(serverGroup, actorType, router);
            ActorFuncMappingCache.TryGetValue(routerKey, out FuncAttribute attribute);
            return attribute;
        }

        internal static Type GetActorTypeByInterface(Type interfaceType)
        {
            ActorInterfaceMappingTypeCache.TryGetValue(interfaceType, out Type type);
            return type;
        }

        internal static Type GetInterfaceTypeByActor(Type actorType)
        {
            ActorMappingInterfaceTypeCache.TryGetValue(actorType, out Type type);
            return type;
        }

        internal static Type GetActorType(string serverGroup, string actorType)
        {
            var actorTypeKey = KeyGenerator.CreateActorTypeKey(serverGroup, actorType);
            ActorTypeCache.TryGetValue(actorTypeKey, out Type type);
            return type;
        }

        internal static Type GetActorInterfaceType(string serverGroup, string actorType)
        {
            var actorTypeKey = KeyGenerator.CreateActorTypeKey(serverGroup, actorType);
            ActorInterfaceTypeCache.TryGetValue(actorTypeKey, out Type type);
            return type;
        }

        internal static ActorTypeInfo GetActorTypeInfo(Type type)
        {
            ActorTypeInfoCache.TryGetValue(type, out ActorTypeInfo typeInfo);
            return typeInfo;
        }

        internal static MethodInfo GetRouterMethod(string serverGroup, string actorType, string router)
        {
            var routerKey = KeyGenerator.CreateActorRouterKey(serverGroup, actorType, router);
            ActorRouterMethodCache.TryGetValue(routerKey, out MethodInfo method);
            return method;
        }

        internal static string GetRouter(string serverGroup, string actorType, string methodName)
        {
            string routerMappingKey = KeyGenerator.CreateActorRouterKey(serverGroup, actorType, methodName);
            ActorRouterMappingCache.TryGetValue(routerMappingKey, out string router);
            return router;
        }

        internal static ParameterInfo[] GetRouterMethodParameters(string serverGroup, string actorType, string router)
        {
            var routerKey = KeyGenerator.CreateActorRouterKey(serverGroup, actorType, router);
            ActorRouterMethodParameterCache.TryGetValue(routerKey, out ParameterInfo[] parameters);
            return parameters;
        }

        internal static IActor GetActor(Type type, string actorId)
        {
            if (!TypeActorInstantiationCache.TryGetValue(type, out ConcurrentDictionary<string, IActor> typeList))
                return null;

            typeList.TryGetValue(actorId, out IActor actor);
            return actor;
        }

        internal static IActor GetActor(string serverGroup, string actorType, string actorId)
        {
            var actorKey = KeyGenerator.CreateActorKey(serverGroup, actorType, actorId);
            ActorInstantiationCache.TryGetValue(actorKey, out IActor actor);
            return actor;
        }

        internal static IActor GetLocalActor(string serverGroup, string actorType, string actorId)
        {
            var actorKey = KeyGenerator.CreateActorKey(serverGroup, actorType, actorId);
            if (!ActorInstantiationCache.TryGetValue(actorKey, out IActor actor))
                return null;

            if (actor.IsLocal)
            {
                return actor;
            }
            else
            {
                return null;
            }
        }

        internal static void AddActor(string serverGroup, string actorType, string actorId, IActor actor)
        {
            var actorKey = KeyGenerator.CreateActorKey(serverGroup, actorType, actorId);
            ActorInstantiationCache.AddOrUpdate(actorKey, actor, (k, v) => actor);
            var type = actor.GetType();
            if(!TypeActorInstantiationCache.TryGetValue(type, out ConcurrentDictionary<string, IActor> typeList))
            {
                typeList = new ConcurrentDictionary<string, IActor>();
                TypeActorInstantiationCache.AddOrUpdate(type, typeList, (k, v) => typeList);
            }
            typeList.AddOrUpdate(actorId, actor, (k, v) => actor);
        }

        internal static void Remove(string serverGroup, string actorType, string actorId)
        {
            var actorKey = KeyGenerator.CreateActorKey(serverGroup, actorType, actorId);
            if (ActorInstantiationCache.TryRemove(actorKey, out IActor actor))
            {
                var type = actor.GetType();
                if (TypeActorInstantiationCache.TryGetValue(type, out ConcurrentDictionary<string, IActor> typeList))
                {
                    typeList.TryRemove(actorId, out _);
                }

                if (ActorFuncMappingCache.TryGetValue(actorKey, out FuncAttribute func))
                    func.RemoveParameters(actor);
            }
        }
    }
}
